﻿using System;
using System.Collections.Generic;
using System.Text;
using Un4seen.Bass;

namespace mxplay
{
    abstract class BassChannelBase
    {
        public int ChannelHandle
        { private set; get; }

        public BassCore Core
        { private set; get; }

        internal abstract int CreateHandle();

        #region basic properties
        internal virtual TimeSpan LengthTime
        {
            get
            {
                return TimeSpan.FromSeconds(Bass.BASS_ChannelBytes2Seconds(ChannelHandle, Bass.BASS_ChannelGetLength(ChannelHandle)));
            }
        }

        internal virtual long LengthBytes
        {
            get
            {
                return Bass.BASS_ChannelGetLength(ChannelHandle);
            }
        }

        internal BASSFlag Flags
        {
            get
            {
                return Bass.BASS_ChannelFlags(ChannelHandle, BASSFlag.BASS_DEFAULT, BASSFlag.BASS_DEFAULT);
            }
        }

        internal void FlagAdd(BASSFlag flag)
        {
            Bass.BASS_ChannelFlags(ChannelHandle, flag, flag);
        }

        internal void FlagRemove(BASSFlag flag)
        {
            Bass.BASS_ChannelFlags(ChannelHandle, BASSFlag.BASS_DEFAULT, flag);
        }

        internal virtual bool Loop
        {
            get
            {
                return (Flags & BASSFlag.BASS_SAMPLE_LOOP) == BASSFlag.BASS_SAMPLE_LOOP;
            }
            set
            {
                if (value)
                {
                    FlagAdd(BASSFlag.BASS_SAMPLE_LOOP);
                }
                else
                {
                    FlagRemove(BASSFlag.BASS_SAMPLE_LOOP);
                }
            }
        }

        internal virtual bool Autofree
        {
            get
            {
                return (Flags & BASSFlag.BASS_STREAM_AUTOFREE) == BASSFlag.BASS_STREAM_AUTOFREE;
            }
            set
            {
                if (value)
                {
                    FlagAdd(BASSFlag.BASS_STREAM_AUTOFREE);
                }
                else
                {
                    FlagRemove(BASSFlag.BASS_STREAM_AUTOFREE);
                }
            }
        }

        internal virtual bool RestrateDownloadRate
        {
            get
            {
                return (Flags & BASSFlag.BASS_STREAM_RESTRATE) == BASSFlag.BASS_STREAM_RESTRATE;
            }
            set
            {
                if (value)
                {
                    FlagAdd(BASSFlag.BASS_STREAM_RESTRATE);
                }
                else
                {
                    FlagRemove(BASSFlag.BASS_STREAM_RESTRATE);
                }
            }
        }

        internal float EaxMixWetDry
        {
            get
            {
                float ret=0;
                bool res= Bass.BASS_ChannelGetAttribute(ChannelHandle, BASSAttribute.BASS_ATTRIB_EAXMIX, ref ret);
                return ret;
            }
            set
            {
                if (!Bass.BASS_ChannelSetAttribute(ChannelHandle, BASSAttribute.BASS_ATTRIB_EAXMIX, value))
                {
                    BassException.ThrowOnError();
                }
            }
        }

        internal float SampleRate
        {
            get
            {
                float ret=0;
                bool res=Bass.BASS_ChannelGetAttribute(ChannelHandle, BASSAttribute.BASS_ATTRIB_FREQ, ref ret);
                return ret;
            }
            set
            {
                if (!Bass.BASS_ChannelSetAttribute(ChannelHandle, BASSAttribute.BASS_ATTRIB_FREQ, value))
                {
                    BassException.ThrowOnError();
                }
            }
        }

        internal float Pan
        {
            get
            {
                float ret=0;
                bool res=Bass.BASS_ChannelGetAttribute(ChannelHandle, BASSAttribute.BASS_ATTRIB_PAN, ref ret);
                return ret;
            }
            set
            {
                if (!Bass.BASS_ChannelSetAttribute(ChannelHandle, BASSAttribute.BASS_ATTRIB_PAN, value))
                {
                    BassException.ThrowOnError();
                }
            }
        }

        internal float Volume
        {
            get
            {
                float ret=0;
                bool res=Bass.BASS_ChannelGetAttribute(ChannelHandle, BASSAttribute.BASS_ATTRIB_VOL, ref ret);
                return ret;
            }
            set
            {
                if (!Bass.BASS_ChannelSetAttribute(ChannelHandle, BASSAttribute.BASS_ATTRIB_VOL, value))
                {
                    BassException.ThrowOnError();
                }
            }
        }

        internal virtual int DeviceId
        {
            get { return Bass.BASS_ChannelGetDevice(ChannelHandle); }
            set
            {
                if (!Bass.BASS_ChannelSetDevice(ChannelHandle, value))
                {
                    BassException.ThrowOnError();
                }
            }
        }

        internal virtual BASS_CHANNELINFO Info
        {
            get { return Bass.BASS_ChannelGetInfo(ChannelHandle); }
        }

        internal virtual long PositionByte
        {
            get { return Bass.BASS_ChannelGetPosition(ChannelHandle); }
            set
            {
                if (!Bass.BASS_ChannelSetPosition(ChannelHandle, value))
                {
                    BassException.ThrowOnError();
                }
            }
        }

        internal virtual TimeSpan PositionTime
        {
            get { return TimeSpan.FromSeconds(Bass.BASS_ChannelBytes2Seconds(ChannelHandle, PositionByte)); }
            set
            {
                if (!Bass.BASS_ChannelSetPosition(ChannelHandle, value.TotalSeconds))
                {
                    BassException.ThrowOnError();
                }
            }
        }

        internal virtual BASSActive State
        {
            get { return Bass.BASS_ChannelIsActive(ChannelHandle); }
        }

        #endregion

        #region basic methods
        internal virtual int GetChannelRawData(int bytes_wanted, IntPtr buffer_ptr)
        {
            return Bass.BASS_ChannelGetData(ChannelHandle, buffer_ptr, bytes_wanted);
        }
        internal virtual void GetLevel(out short left, out short right)
        {
            int res=Bass.BASS_ChannelGetLevel(ChannelHandle);
            left = (short)Utils.LowWord32(res);
            right = (short)Utils.HighWord32(res);
        }
        internal virtual void Pause()
        {
            if (!Bass.BASS_ChannelPause(ChannelHandle))
            {
                BassException.ThrowOnError();
            }
        }
        internal virtual void Play(bool restart)
        {
            if (!Bass.BASS_ChannelPlay(ChannelHandle, restart))
            {
                BassException.ThrowOnError();
            }
        }
        internal virtual void Play()
        {
            Play(false);
        }
        internal virtual void Stop()
        {
            if (!Bass.BASS_ChannelStop(ChannelHandle)) BassException.ThrowOnError();
        }
        #endregion

        #region sync
        #region download
        internal event EventHandler DownloadDone;
        private int download_done_sync_handle;
        private SYNCPROC download_done_sync_holder;
        private void download_done_sync_proc(int sync_handle, int channel_handle, int data, IntPtr user_data)
        {
            OnDownloadDone();
        }
        protected virtual void OnDownloadDone()
        {
            if (DownloadDone != null) DownloadDone(this, new EventArgs());
        }

        internal virtual bool DownloadDoneEnable
        {
            get { return download_done_sync_handle != 0; }
            set
            {
                if (value)
                {
                    if (download_done_sync_handle == 0)
                    {
                        if (download_done_sync_holder == null) download_done_sync_holder = new SYNCPROC(download_done_sync_proc);
                        download_done_sync_handle = Bass.BASS_ChannelSetSync(ChannelHandle, BASSSync.BASS_SYNC_DOWNLOAD, 0, download_done_sync_holder, IntPtr.Zero);
                        if (download_done_sync_handle == 0)
                        {
                            BassException.ThrowOnError();
                        }
                    }
                }
                else
                {
                    if (download_done_sync_handle != 0)
                    {
                        if (Bass.BASS_ChannelRemoveSync(ChannelHandle, download_done_sync_handle))
                        {
                            download_done_sync_handle = 0;
                        }
                        else
                        {
                            BassException.ThrowOnError();
                        }
                    }
                }
            }
        }
        #endregion
        #region end
        internal event EventHandler EndMix;
        internal event EventHandler EndPlay;
        private SYNCPROC endmix_sync_holder;
        private SYNCPROC endplay_sync_holder;
        private int endmix_sync_handle;
        private int endplay_sync_handle;
        protected virtual void OnEndMix()
        {
            if (EndMix != null) EndMix(this, new EventArgs());
        }
        protected virtual void OnEndPlay()
        {
            if (EndPlay != null) EndPlay(this, new EventArgs());
        }
        private void endmix_sync_proc(int sync_handle, int channel_handle, int data, IntPtr user_data)
        {
            OnEndMix();
        }
        private void endplay_sync_proc(int sync_handle, int channel_handle, int data, IntPtr user_data)
        {
            OnEndPlay();
        }
        internal virtual bool EndEventEnable
        {
            get { return endmix_sync_handle != 0; }
            set
            {
                if (value)
                {
                    if (endmix_sync_handle == 0)
                    {
                        if (endmix_sync_holder == null) endmix_sync_holder = new SYNCPROC(endmix_sync_proc);
                        if (endplay_sync_holder == null) endplay_sync_holder = new SYNCPROC(endplay_sync_proc);
                        endmix_sync_handle = Bass.BASS_ChannelSetSync(ChannelHandle, BASSSync.BASS_SYNC_MIXTIME | BASSSync.BASS_SYNC_END, 0, endmix_sync_holder, IntPtr.Zero);
                        endplay_sync_handle = Bass.BASS_ChannelSetSync(ChannelHandle, BASSSync.BASS_SYNC_END, 0, endplay_sync_holder, IntPtr.Zero);
                    }
                }
                else
                {
                    Bass.BASS_ChannelRemoveSync(ChannelHandle, endmix_sync_handle);
                    Bass.BASS_ChannelRemoveSync(ChannelHandle, endplay_sync_handle);
                    endmix_sync_handle = 0;
                    endplay_sync_handle = 0;
                }
            }
        }
        #endregion
        #region free
        private int free_sync_handle;
        private SYNCPROC free_sync_holder;
        internal event EventHandler Free;
        protected virtual void OnFree()
        {
            if (Free != null) Free(this, new EventArgs());
        }
        private void free_sync_proc(int sync_handle, int channel_handle, int data, IntPtr user_data)
        {
            OnFree();
        }
        internal virtual bool FreeEventEnable
        {
            get { return free_sync_handle != 0; }
            set
            {
                if (value)
                {
                    if (free_sync_handle == 0)
                    {
                        if (free_sync_holder == null) free_sync_holder = new SYNCPROC(free_sync_proc);
                        free_sync_handle = Bass.BASS_ChannelSetSync(ChannelHandle, BASSSync.BASS_SYNC_FREE, 0, free_sync_holder, IntPtr.Zero);
                    }
                }
                else
                {
                    Bass.BASS_ChannelRemoveSync(ChannelHandle, free_sync_handle);
                    free_sync_handle = 0;
                }
            }
        }
        #endregion
        #region metadata updated
        private int meta_sync_handle;
        private SYNCPROC meta_sync_holder;
        internal event EventHandler MetadataUpdated;
        protected virtual void OnMetadataUpdated()
        {
            if (MetadataUpdated != null) MetadataUpdated(this, new EventArgs());
        }
        private void meta_sync_proc(int sync_handle, int channel_handle, int data, IntPtr user_data)
        {
            OnMetadataUpdated();
        }
        internal virtual bool MetadataUpdatedEnable
        {
            get { return meta_sync_handle != 0; }
            set
            {
                if (value)
                {
                    if (meta_sync_handle == 0)
                    {
                        if (meta_sync_holder == null) meta_sync_holder = new SYNCPROC(meta_sync_proc);
                        meta_sync_handle = Bass.BASS_ChannelSetSync(ChannelHandle, BASSSync.BASS_SYNC_META, 0, meta_sync_holder, IntPtr.Zero);
                        if (meta_sync_handle == 0) BassException.ThrowOnError();
                    }
                }
                else
                {
                    if (meta_sync_handle != 0)
                    {
                        if (Bass.BASS_ChannelRemoveSync(ChannelHandle, meta_sync_handle))
                        {
                            meta_sync_handle = 0;
                        }
                        else
                        {
                            BassException.ThrowOnError();
                        }
                    }
                }
            }
        }
        #endregion
        #region ogg logical change
        private int ogg_sync_handle;
        private SYNCPROC ogg_sync_holder;
        internal event EventHandler OggStreamChange;
        protected virtual void OnOggStreamChange()
        {
            if (OggStreamChange != null) OggStreamChange(this, new EventArgs());
        }
        private void ogg_sync_proc(int sync_handle, int channel_handle, int data, IntPtr user_data)
        {
            OnOggStreamChange();
        }
        internal virtual bool OggStreamChangeEventEnable
        {
            get { return ogg_sync_handle != 0; }
            set
            {
                if (value)
                {
                    if (ogg_sync_handle == 0)
                    {
                        if (ogg_sync_holder == null) ogg_sync_holder = new SYNCPROC(ogg_sync_proc);
                        ogg_sync_handle = Bass.BASS_ChannelSetSync(ChannelHandle, BASSSync.BASS_SYNC_OGG_CHANGE, 0, ogg_sync_holder, IntPtr.Zero);
                        if (ogg_sync_handle == 0) BassException.ThrowOnError();
                    }
                }
                else
                {
                    if (ogg_sync_handle != 0)
                    {
                        if (Bass.BASS_ChannelRemoveSync(ChannelHandle, ogg_sync_handle))
                        {
                            ogg_sync_handle = 0;
                        }
                        else
                        {
                            BassException.ThrowOnError();
                        }
                    }
                }
            }
        }
        #endregion
        #region stalled
        private int stall_sync_handle;
        private SYNCPROC stall_sync_holder;
        internal event EventHandler Stalled;
        internal event EventHandler ResumeAfterStall;
        protected virtual void OnStall(bool resume)
        {
            if (resume)
            {
                if (ResumeAfterStall != null) ResumeAfterStall(this, new EventArgs());
            }
            else
            {
                if (Stalled != null) Stalled(this, new EventArgs());
            }
        }
        private void stall_sync_proc(int sync_handle, int channel_handle, int data, IntPtr user_data)
        {
            OnStall(data != 0);
        }
        internal virtual bool StallEventEnable
        {
            get { return stall_sync_handle != 0; }
            set
            {
                if (value)
                {
                    if (stall_sync_handle == 0)
                    {
                        if (stall_sync_holder == null) stall_sync_holder = new SYNCPROC(stall_sync_proc);
                        stall_sync_handle = Bass.BASS_ChannelSetSync(ChannelHandle, BASSSync.BASS_SYNC_STALL, 0, stall_sync_holder, IntPtr.Zero);
                        if (stall_sync_handle == 0) BassException.ThrowOnError();
                    }
                }
                else
                {
                    if (stall_sync_handle != 0)
                    {
                        if (Bass.BASS_ChannelRemoveSync(ChannelHandle, stall_sync_handle))
                        {
                            stall_sync_handle = 0;
                        }
                        else
                        {
                            BassException.ThrowOnError();
                        }
                    }
                }
            }
        }
        #endregion
        #endregion

        #region slide
        private int slide_volume_sync_handle;
        private EventHandler slide_volume_callback;
        private SYNCPROC slide_volume_sync_holder;
        private void slide_volume_sync_proc(int sync_handle, int channel_handle, int data, IntPtr user_data)
        {
            if (slide_volume_callback != null) slide_volume_callback(this, new EventArgs());
        }
        internal virtual void SlideVol(float new_volume, TimeSpan time, EventHandler done)
        {
            if (Bass.BASS_ChannelSlideAttribute(ChannelHandle, BASSAttribute.BASS_ATTRIB_VOL, new_volume, (int)time.TotalMilliseconds))
            {
                slide_volume_callback = done;
                if (slide_volume_sync_holder == null) slide_volume_sync_holder = new SYNCPROC(slide_volume_sync_proc);
                slide_volume_sync_handle = Bass.BASS_ChannelSetSync(ChannelHandle, BASSSync.BASS_SYNC_SLIDE | BASSSync.BASS_SYNC_ONETIME, 0, slide_volume_sync_holder, IntPtr.Zero);
            }
            else
            {
                BassException.ThrowOnError();
            }
        }
        #endregion
    }
}
